package net.sudot.chess.business.wechat.api;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.json.GsonHelper;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import me.chanjar.weixin.mp.api.WxMpCardService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpCardServiceImpl;
import net.sudot.chess.business.wechat.dto.card.CardBatchgetResult;
import net.sudot.chess.business.wechat.dto.card.CardCreateResult;
import net.sudot.chess.business.wechat.dto.card.CardListResult;
import net.sudot.chess.business.wechat.dto.card.CardQrCodeTicketResult;
import net.sudot.chess.business.wechat.dto.card.CardResult;
import net.sudot.chess.business.wechat.dto.card.CardWhiteListResult;
import net.sudot.chess.exception.BusinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 微信卡券API
 *
 * @author tangjialin on 2017-05-10
 */
public class WechatCardApiService extends WxMpCardServiceImpl implements WxMpCardService {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private WechatApiService wechatApiService;

    public WechatCardApiService(WxMpService wxMpService) {
        super(wxMpService);
        wechatApiService = (WechatApiService) wxMpService;
    }

    /**
     * <pre>
     *     创建卡券投放二维码接口(投放单张卡券)
     *     开发者可调用该接口生成一张卡券二维码供用户扫码后添加卡券到卡包。
     *     自定义Code码的卡券调用接口时，POST数据中需指定code，非自定义code不需指定，指定openid同理。指定后的二维码只能被用户扫描领取一次。
     *
     *     获取二维码ticket后，开发者可用通过ticket换取二维码接口换取二维码图片详情。
     *     详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283&token=&lang=zh_CN
     *
     *     注意事项：
     *
     *     1.自定义code的卡券，生成的二维码每次只能领取一次，若开发者想要使用自己的串码系统并且想要用微信的二维码投放，须先将自定义code导入；
     *     2.领取多张的二维码一次最多填入5个card_id，否则报错。
     *     POST数据
     *     开发者可以设置扫描二维码领取单张卡券，此时POST数据为：
     *     {
     *       "action_name": "QR_CARD",
     *       "expire_seconds": 1800,
     *       "action_info": {
     *         "card": {
     *           "card_id": "pFS7Fjg8kV1IdDz01r4SQwMkuCKc",
     *           "code": "198374613512",
     *           "openid": "oFS7Fjl0WsZ9AMZqrI80nbIq8xrA",
     *           "is_unique_code": false ,
     *           "outer_str":"12b"
     *         }
     *       }
     *     }
     *
     *     当开发者设置扫描二维码领取多张卡券，此时POST数据为：
     *     {
     *       "action_name": "QR_MULTIPLE_CARD",
     *       "action_info": {
     *         "multiple_card": {
     *           "card_list": [
     *           {
     *             "card_id": "p1Pj9jgj3BcomSgtuW8B1wl-wo88",
     *             "code":"2392583481",
     *             "outer_str":"12b"
     *           },
     *           {
     *             "card_id": "p1Pj9jgj3BcomSgtuW8B1wl-wo98",
     *             "code":"2392583482",
     *             "outer_str":"12b"
     *           }
     *           ]
     *         }
     *       }
     *     }
     *     </pre>
     *
     * @param openid        指定领取者的openid，只有该用户能领取。bind_openid字段为true的卡券必须填写，非指定openid不必填写。
     * @param cardId        卡券ID
     * @param code          卡券Code码,use_custom_code字段为true的卡券必须填写，非自定义code和导入code模式的卡券不必填写。
     * @param expireSeconds 过期时间(秒)
     * @param isUniqueCode  指定下发二维码，生成的二维码随机分配一个code，领取后不可再次扫描。填写true或false。默认false，注意填写该字段时，卡券须通过审核且库存不为0
     * @param outerStr      outer_id字段升级版本，字符串类型，用户首次领卡时，会通过领取事件推送给商户；
     *                      <br/>对于会员卡的二维码，用户每次扫码打开会员卡后点击任何url，会将该值拼入url中，方便开发者定位扫码来源
     * @return
     * @throws WxErrorException
     */
    public CardQrCodeTicketResult createQrcode(String openid, String cardId, String code, int expireSeconds, boolean isUniqueCode, String outerStr) throws WxErrorException {
        if (cardId == null || cardId.isEmpty()) { throw new BusinessException("卡券ID不能为空"); }
        String url = "https://api.weixin.qq.com/card/qrcode/create";
        JsonObject param = new JsonObject();
        param.addProperty("action_name", "QR_CARD");
        param.addProperty("expire_seconds", expireSeconds);

        JsonObject action_info = new JsonObject();
        param.add("action_info", action_info);

        JsonObject card = new JsonObject();
        action_info.add("card", card);

        if (openid != null && !openid.isEmpty()) {
            card.addProperty("openid", openid);
        }
        card.addProperty("card_id", cardId);
        if (code != null && !code.isEmpty()) {
            card.addProperty("code", code);
        }
        card.addProperty("is_unique_code", isUniqueCode);
        if (outerStr != null && !outerStr.isEmpty()) {
            card.addProperty("outer_str", outerStr);
        }
        String post = wechatApiService.post(url, param.toString());
        JsonObject jsonObject = JsonParser.parseString(post).getAsJsonObject();

        CardQrCodeTicketResult result = new CardQrCodeTicketResult();
        result.setErrorCode(GsonHelper.getInteger(jsonObject, "errcode"));
        result.setErrorMsg(GsonHelper.getString(jsonObject, "errmsg"));
        result.setTicket(GsonHelper.getString(jsonObject, "ticket"));
        result.setUrl(GsonHelper.getString(jsonObject, "url"));
        result.setShowQrcodeUrl(GsonHelper.getString(jsonObject, "show_qrcode_url"));
        return result;
    }

    /**
     * <pre>
     *     获取用户已领取卡券接口
     *     用于获取用户卡包里的，属于该appid下所有可用卡券，包括正常状态和未生效状态。
     *     详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283&token=&lang=zh_CN
     * </pre>
     *
     * @param openid 需要查询的用户openid
     * @param cardId 卡券ID。不填写时默认查询当前appid下的卡券
     * @return
     * @throws WxErrorException
     */
    public CardListResult getCardlist(String openid, String cardId) throws WxErrorException {
        if (openid == null || openid.isEmpty()) { throw new BusinessException("openid不能为空"); }
        String url = "https://api.weixin.qq.com/card/user/getcardlist";
        JsonObject param = new JsonObject();
        param.addProperty("openid", openid);
        if (cardId != null && !cardId.isEmpty()) {
            param.addProperty("card_id", cardId);
        }
        String post = wechatApiService.post(url, param.toString());
        JsonObject jsonObject = JsonParser.parseString(post).getAsJsonObject();

        CardListResult result = new CardListResult();
        result.setErrorCode(GsonHelper.getInteger(jsonObject, "errcode"));
        result.setErrorMsg(GsonHelper.getString(jsonObject, "errmsg"));
        result.setHasShareCard(GsonHelper.getBoolean(jsonObject, "has_share_card"));
        JsonArray cardList = jsonObject.getAsJsonArray("card_list");
        if (cardList != null) {
            List<CardResult> cardResultList = new ArrayList<>(cardList.size());
            for (JsonElement jsonElement : cardList) {
                JsonObject cardJsonObject = jsonElement.getAsJsonObject();
                CardResult cardResult = new CardResult();
                cardResult.setCardId(GsonHelper.getString(cardJsonObject, "card_id"));
                cardResult.setCode(GsonHelper.getString(cardJsonObject, "code"));
                cardResultList.add(cardResult);
            }
            result.setCardIds(cardResultList);
        }
        return result;
    }

    /**
     * <pre>
     * 获取所有卡券信息
     *
     * 详见:https://mp.weixin.qq.com/wiki?action=doc&id=mp1451025272&t=0.3258788006260265#4
     * </pre>
     *
     * @param offset 查询卡列表的起始偏移量，从0开始，即offset: 5是指从从列表里的第六个开始读取
     * @param count  需要查询的卡片的数量（数量最大50）
     * @return
     * @throws WxErrorException
     */
    public CardBatchgetResult batchgetCard(int offset, int count) throws WxErrorException {
        return batchgetCard(offset, count, null);
    }

    /**
     * 获取所有卡券信息
     *
     * @param offset 查询卡列表的起始偏移量，从0开始，即offset: 5是指从从列表里的第六个开始读取
     * @param count  需要查询的卡片的数量（数量最大50）
     * @param status 指定状态的卡券列表
     *               CARD_STATUS_NOT_VERIFY:待审核
     *               CARD_STATUS_VERIFY_FAIL:审核失败
     *               CARD_STATUS_VERIFY_OK 通过审核
     *               CARD_STATUS_DELETE:卡券被商户删除
     *               CARD_STATUS_DISPATCH:在公众平台投放过的卡券
     * @return
     * @throws WxErrorException
     */
    @SuppressWarnings("unchecked")
    public CardBatchgetResult batchgetCard(int offset, int count, Set<String> status) throws WxErrorException {
        if (count > 50 || count <= 0) { throw new BusinessException("获取卡券数量不能大于50或小于1"); }
        String url = "https://api.weixin.qq.com/card/batchget";
        JsonObject param = new JsonObject();
        param.addProperty("offset", offset);
        param.addProperty("count", count);
        Gson gson = WxGsonBuilder.create();
        if (status != null && !status.isEmpty()) {
            param.addProperty("status_list", gson.toJson(status));
        }
        String responseContent = wechatApiService.post(url, param.toString());
        JsonObject jsonObject = JsonParser.parseString(responseContent).getAsJsonObject();

        CardBatchgetResult cardBatchgetResult = new CardBatchgetResult();
        cardBatchgetResult.setErrorCode(GsonHelper.getInteger(jsonObject, "errcode"));
        cardBatchgetResult.setErrorMsg(GsonHelper.getString(jsonObject, "errmsg"));
        cardBatchgetResult.setTotal(GsonHelper.getInteger(jsonObject, "total_num"));
        JsonArray cardIdList = jsonObject.getAsJsonArray("card_id_list");
        if (cardIdList != null) {
            cardBatchgetResult.setCardIds(gson.fromJson(cardIdList, List.class));
        }
        return cardBatchgetResult;
    }

    /**
     * <pre>
     *     更改Code接口
     *     为确保转赠后的安全性，微信允许自定义Code的商户对已下发的code进行更改。
     *     注：为避免用户疑惑，建议仅在发生转赠行为后（发生转赠后，微信会通过事件推送的方式告知商户被转赠的卡券Code）对用户的Code进行更改。
     *     详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283&token=&lang=zh_CN
     * </pre>
     *
     * @param cardId  卡券ID。自定义Code码卡券为必填
     * @param code    需变更的Code码
     * @param newCode 变更后的有效Code码
     * @return
     * @throws WxErrorException
     */
    public void updateCode(String cardId, String code, String newCode) throws WxErrorException {
        if (code == null || code.isEmpty()) { throw new BusinessException("修改的卡券code不能为空"); }
        if (newCode == null || newCode.isEmpty()) { throw new BusinessException("卡券新的code不能为空"); }
        String url = "https://api.weixin.qq.com/card/code/update";
        JsonObject param = new JsonObject();
        param.addProperty("code", code);
        param.addProperty("new_code", newCode);
        if (cardId != null && !cardId.isEmpty()) {
            param.addProperty("card_id", cardId);
        }
        wechatApiService.post(url, param.toString());
    }

    /**
     * <pre>
     *     删除卡券接口
     *     删除卡券接口允许商户删除任意一类卡券。删除卡券后，该卡券对应已生成的领取用二维码、添加到卡包JS API均会失效。
     *     注意：如用户在商家删除卡券前已领取一张或多张该卡券依旧有效。
     *     即删除卡券不能删除已被用户领取，保存在微信客户端中的卡券。
     *     详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283&token=&lang=zh_CN
     * </pre>
     *
     * @param cardId 卡券ID
     * @return
     * @throws WxErrorException
     */
    public void delete(String cardId) throws WxErrorException {
        if (cardId == null || cardId.isEmpty()) { throw new BusinessException("删除的卡券ID不能为空"); }
        String url = "https://api.weixin.qq.com/card/delete";
        JsonObject param = new JsonObject();
        param.addProperty("card_id", cardId);
        wechatApiService.post(url, param.toString());
    }

    /**
     * <pre>
     *     设置卡券失效接口
     *     为满足改票、退款等异常情况，可调用卡券失效接口将用户的卡券设置为失效状态。
     *
     *     注意事项：
     *     1.设置卡券失效的操作不可逆，即无法将设置为失效的卡券调回有效状态，商家须慎重调用该接口。
     *     2.商户调用失效接口前须与顾客事先告知并取得同意，否则因此带来的顾客投诉，
     *       微信将会按照<a href="https://mp.weixin.qq.com/cgi-bin/readtemplate?t=cardticket/faq_tmpl&type=info&token=1668276868&lang=zh_CN#0dot5">《微信运营处罚规则》</a>进行处罚。
     *
     *     POST数据
     *     非自定义卡券的请求
     *     {
     *       "code": "12312313",
     *       "reason":"退款"
     *     }
     *     或自定义code卡券的请求。
     *     {
     *       "code": "12312313",
     *       "card_id": "pFS7Fjg8kV1IdDz01r4SQwMkuCKc"
     *     }
     *     详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283&token=&lang=zh_CN
     * </pre>
     *
     * @param cardId 卡券ID。自定义Code码卡券为必填
     * @param code   设置失效的Code码
     * @param reason 失效理由
     * @return
     * @throws WxErrorException
     */
    public void unavailable(String cardId, String code, String reason) throws WxErrorException {
        if (code == null || code.isEmpty()) { throw new BusinessException("修改的卡券code不能为空"); }
        String url = "https://api.weixin.qq.com/card/code/unavailable";
        JsonObject param = new JsonObject();
        param.addProperty("code", code);
        if (cardId != null && !cardId.isEmpty()) {
            param.addProperty("card_id", cardId);
        }
        if (reason != null && !reason.isEmpty()) {
            param.addProperty("reason", reason);
        }
        wechatApiService.post(url, param.toString());
    }

    /**
     * <pre>
     *     创建会员卡接口
     *
     *     调用接口成功创建会员卡后，会由系统自动提交审核，审核结果将在三个工作日内以事件形式告知开发者，同时支持调用接口主动查询卡券状态。
     *
     *     1. 仅部分类目可创建会员卡，非开放类目将返回错误码，类目明细见会员卡公告
     *     2.创建会员卡时，需设置会员卡激活后呈现的信息类目，
     *       目前支持积分、余额、等级、优惠券、里程、印花、成就、折扣等类型，
     *       微信6.2以上版本显示会员信息类目的上限为3个，
     *       即创建时类目字段supply_bonus、supply_balance、custom_field1、custom_field2、custom_field3最多选择三项填写。
     *     3.创建卡券时，开发者填入的时间戳须注意时间戳溢出时间，设置的时间戳须早于2038年1月19日，若要设置更久的有效期，可以选择永久有效类型的有效期。
     *
     *     因其内容字段较多,请参考官方文档处理
     *     详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283&token=&lang=zh_CN
     * </pre>
     *
     * @param json
     * @return
     * @throws WxErrorException
     */
    public CardCreateResult create(String json) throws WxErrorException {
        String url = "https://api.weixin.qq.com/card/create";
        String post = wechatApiService.post(url, json);
        JsonObject jsonObject = JsonParser.parseString(post).getAsJsonObject();
        CardCreateResult createResult = new CardCreateResult();
        createResult.setCardId(GsonHelper.getString(jsonObject, "card_id"));
        return createResult;
    }

    /**
     * 更改卡券信息接口
     * 支持更新所有卡券类型的部分通用字段及特殊卡券（会员卡、飞机票、电影票、会议门票）中特定字段的信息。
     *
     * @param json
     * @return
     * @throws WxErrorException
     */
    public void update(String json) throws WxErrorException {
        String url = "https://api.weixin.qq.com/card/update";
        wechatApiService.post(url, json);
    }

    /**
     * <pre>
     *     设置测试白名单
     *     由于卡券有审核要求，为方便公众号调试，可以设置一些测试帐号，这些帐号可领取未通过审核的卡券，体验整个流程。
     *
     *     开发者注意事项
     *     1.同时支持“openid”、“username”两种字段设置白名单，总数上限为10个。
     *     2.设置测试白名单接口为全量设置，即测试名单发生变化时需调用该接口重新传入所有测试人员的ID.
     *     3.白名单用户领取该卡券时将无视卡券失效状态，请开发者注意。
     *     详见:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025283&token=&lang=zh_CN
     * </pre>
     *
     * @param openIdList   测试的openid列表
     * @param usernameList 测试的微信号列表
     * @return
     * @throws WxErrorException
     */
    @SuppressWarnings("unchecked")
    public CardWhiteListResult testwhitelist(List<String> openIdList, List<String> usernameList) throws WxErrorException {
        String url = "https://api.weixin.qq.com/card/testwhitelist/set";
        Map<String, List<String>> listMap = new HashMap<>(2, 1.0F);
        if (openIdList != null && !openIdList.isEmpty()) {
            listMap.put("openid", openIdList);
        }
        if (usernameList != null && !usernameList.isEmpty()) {
            listMap.put("username", usernameList);
        }
        if (listMap.isEmpty()) { throw new BusinessException("openid或微信号至少存在一个"); }
        Gson gson = WxGsonBuilder.create();
        String post = wechatApiService.post(url, gson.toJson(listMap));
        JsonObject jsonObject = JsonParser.parseString(post).getAsJsonObject();
        CardWhiteListResult result = new CardWhiteListResult();
        result.setErrorCode(GsonHelper.getInteger(jsonObject, "errcode"));
        result.setErrorMsg(GsonHelper.getString(jsonObject, "errmsg"));
        result.setWhiteListSize(GsonHelper.getInteger(jsonObject, "white_list_size"));
        result.setSuccessOpenid(gson.fromJson(jsonObject.getAsJsonArray("success_openid"), List.class));
        result.setSuccessUsername(gson.fromJson(jsonObject.getAsJsonArray("success_username"), List.class));
        return result;
    }

}
